package com.vr.cloudpicture.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vr.cloudpicture.api.aliyunoutpicture.AliYunAiApi;
import com.vr.cloudpicture.api.aliyunoutpicture.model.CreateOutPaintingTaskRequest;
import com.vr.cloudpicture.api.aliyunoutpicture.model.CreateOutPaintingTaskResponse;
import com.vr.cloudpicture.common.PageResult;
import com.vr.cloudpicture.constant.GlobalConstant;
import com.vr.cloudpicture.enums.PictureReviewStatusEnum;
import com.vr.cloudpicture.enums.SortEnum;
import com.vr.cloudpicture.enums.SpaceTypeEnum;
import com.vr.cloudpicture.exception.BusinessException;
import com.vr.cloudpicture.exception.ErrorCode;
import com.vr.cloudpicture.exception.ThrowUtils;
import com.vr.cloudpicture.manager.CacheManager;
import com.vr.cloudpicture.manager.CosManager;
import com.vr.cloudpicture.manager.auth.SpaceUserAuthManager;
import com.vr.cloudpicture.manager.auth.StpKit;
import com.vr.cloudpicture.manager.auth.model.SpaceUserPermissionConstant;
import com.vr.cloudpicture.manager.upload.FilePictureUpload;
import com.vr.cloudpicture.manager.upload.PictureUploadTemplate;
import com.vr.cloudpicture.manager.upload.UrlPictureUpload;
import com.vr.cloudpicture.mapper.PictureMapper;
import com.vr.cloudpicture.model.dto.DeleteRequest;
import com.vr.cloudpicture.model.dto.file.UploadPictureResult;
import com.vr.cloudpicture.model.dto.picture.*;
import com.vr.cloudpicture.model.dto.user.UserLogin;
import com.vr.cloudpicture.model.entity.Picture;
import com.vr.cloudpicture.model.entity.Space;
import com.vr.cloudpicture.model.entity.User;
import com.vr.cloudpicture.model.vo.PictureVO;
import com.vr.cloudpicture.model.vo.UserVO;
import com.vr.cloudpicture.service.IPictureService;
import com.vr.cloudpicture.service.ISpaceService;
import com.vr.cloudpicture.service.IUserService;
import com.vr.cloudpicture.util.ColorSimilarityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import reactor.util.annotation.Nullable;

import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author hzh
 * @description 针对表【picture(图片)】的数据库操作Service实现
 * @createDate 2025-05-07 16:24:42
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture> implements IPictureService {
    private final FilePictureUpload filePictureUpload;
    private final UrlPictureUpload urlPictureUpload;
    private final PictureMapper pictureMapper;
    private final IUserService userService;
    private final CacheManager cacheManager;
    private final ISpaceService spaceService;
    private static final String UPLOAD_PUBLIC_PATH_PREFIX_FORMAT = "public/%s";
    private static final String UPLOAD_SPACE_PATH_PREFIX_FORMAT = "space/%s";
    // 爬取地址
    @Value("${cloud-picture.crawler-url}")
    private String crawlerUrl = "https://cn.bing.com/images/async?q=%sfirst=%s&count=35&mmasync=1";
    @Lazy
    @Autowired
    private IPictureService pictureService;
    @Autowired
    private CosManager cosManager;
    @Autowired
    private TransactionTemplate transactionTemplate;
    private final ThreadPoolTaskExecutor customExecutor;
    private final AliYunAiApi aliYunAiApi;
    @Autowired
    private SpaceUserAuthManager spaceUserAuthManager;


    @Override
    public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, UserLogin loginUser) {
        ThrowUtils.throwIf(inputSource == null, ErrorCode.PARAMS_ERROR, "图片为空");
        ThrowUtils.throwIf(pictureUploadRequest == null, ErrorCode.PARAMS_ERROR, "上传图片信息不能为空");
        // 拿到上传模板,默认是文件上传
        PictureUploadTemplate pictureUploadTemplate = filePictureUpload;
        if (inputSource instanceof String) {
            // 表示编译类型是String类型，那么用url方式上传
            pictureUploadTemplate = urlPictureUpload;
        }
        // 上传图片
        Long spaceId = pictureUploadRequest.getSpaceId();
        // 根据space是否为null来决定上传到公共图库还是私有空间
        String uploadPathPrefix = spaceId == null ?
                String.format(UPLOAD_PUBLIC_PATH_PREFIX_FORMAT, loginUser.getId()) :
                String.format(UPLOAD_SPACE_PATH_PREFIX_FORMAT, spaceId);
        UploadPictureResult uploadPictureResult;
        Long pictureId = pictureUploadRequest.getId();
        Picture picture = null;
        if (pictureId != null && pictureId > 0) {
            // 更新
            Picture oldPicture = pictureMapper.selectById(pictureId);
            ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
            // 校验权限,注释用so-token注解替换了
            // checkPictureAuth(loginUser, oldPicture);
            if (spaceId != null) {
                // 携带的话就必须与原图一致
                ThrowUtils.throwIf(!spaceId.equals(oldPicture.getSpaceId()), ErrorCode.PARAMS_ERROR, "空间id不一致");
                Space space = spaceService.getById(spaceId);
                // 检查空间
                spaceService.checkSpace(space);
            } else {
                // 没有带spaceId用原图的spaceId
                spaceId = oldPicture.getSpaceId();
            }
            // 上传图片
            uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);
            picture = this.buildPicture(oldPicture, uploadPictureResult, loginUser.getId(), spaceId);
        } else {
            // 新增
            if (spaceId != null) {
                Space space = spaceService.getById(spaceId);
                spaceService.checkSpace(space);
            }
            // 构建校验所需参数
            picture = Picture.builder()
                    .userId(loginUser.getId())
                    .spaceId(spaceId)
                    .build();
            // 校验权限,用了so-token
            // checkPictureAuth(loginUser, picture);
            // 校验通过后上传图片
            uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);
            picture = this.buildPicture(null, uploadPictureResult, loginUser.getId(), spaceId);
        }
        // 填充审核参数,形参和实参是同一个对象所以可以不返回
        this.fillReviewParams(picture, loginUser);

        String picName = pictureUploadRequest.getName();
        // batch上传时，图片有名称的，那么就使用上传时图片名称
        if (StrUtil.isNotBlank(picName)) {
            picture.setName(picName);
        }
        Picture finalPicture = picture;
        Long finalSpaceId = picture.getSpaceId();
        // 开启事务
        transactionTemplate.execute((status) -> {
            // 更新或者新增
            boolean result = pictureMapper.insertOrUpdate(finalPicture);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败");
            // 更新额度
            if (finalSpaceId != null) {
                boolean update = spaceService.lambdaUpdate()
                        .eq(Space::getId, finalSpaceId)
                        .setSql("total_count = total_count + 1")
                        .setSql("total_size = total_size + " + finalPicture.getPicSize())
                        .update();
                ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "更新额度失败");
            }
            return finalPicture;
        });
        return PictureVO.objToVO(picture);
    }


    @Override
    public PictureVO getPictureVOById(Long id, HttpServletRequest request) {
        ThrowUtils.throwIf(id == null, ErrorCode.PARAMS_ERROR, "图片id不能为空");
        Picture picture = pictureMapper.selectById(id);
        ThrowUtils.throwIf(picture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
        // 校验权限
        Long spaceId = picture.getSpaceId();
        Space space=null;
        if (spaceId != null) {
            // 私有空间,得有权限才能访问
            // checkPictureAuth(loginUser, picture);
            // 用so-token的编程时权限校验
            boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW);
            ThrowUtils.throwIf(!hasPermission, ErrorCode.NOT_AUTH_ERROR);
            space = spaceService.getById(spaceId);
            ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
        }
        UserLogin loginUser = userService.getLoginUser(request);
        PictureVO pictureVO = PictureVO.objToVO(picture);
        pictureVO.setUser(userService.getUserVOById(picture.getUserId()));
        // 获取权限，即使公共图库也要有权限，方便前端使用
        pictureVO.setPermissions(spaceUserAuthManager.getPermissionList(space, loginUser));
        return pictureVO;
    }

    @Override
    public PageResult<Picture> listPictureByPage(PictureQueryRequest pictureQueryRequest) {
        // 开启分页
        Page<Picture> page = Page.of(pictureQueryRequest.getPageNum(), pictureQueryRequest.getPageSize());
        LambdaQueryWrapper<Picture> pageQueryWrapper = this.buildPageQueryWrapper(pictureQueryRequest);
        Page<Picture> pageResult = this.page(page, pageQueryWrapper);
        return new PageResult<Picture>()
                .setPageNum(pageResult.getCurrent())
                .setPageSize(pageResult.getSize())
                .setTotal(pageResult.getTotal())
                .setRecords(pageResult.getRecords());
    }


    @Override
    public PageResult<PictureVO> listPictureVOByPage(PictureQueryRequest pictureQueryRequest, HttpServletRequest request) {
        // 防止爬虫
        ThrowUtils.throwIf(pictureQueryRequest.getPageSize() >= 20, ErrorCode.PARAMS_ERROR, "一次最多查询20条数据");
        // 构建缓存key
        String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
        String hashKey = DigestUtil.md5Hex(queryCondition.getBytes());
        String cacheKey = GlobalConstant.CACHE_PREFIX + hashKey;
        Long spaceId = pictureQueryRequest.getSpaceId();
         AtomicReference<UserLogin> loginUser = new AtomicReference<>();
        // 公共图库放于缓存里提高效率，私有图库没必要
        String cacheValue = cacheManager.getOrLoad(cacheKey, () -> {
            if (spaceId == null) {
                // 公共图库，普通用户默认只能查询过审的数据
                pictureQueryRequest.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
                pictureQueryRequest.setNullSpaceId(true);
            } else {
                // 私有空间,只有本人才能看
                loginUser.set(userService.getLoginUser(request));
                ThrowUtils.throwIf(loginUser.get() == null, ErrorCode.NOT_LOGIN_ERROR, "必须登录才有权限");
                Long userId = loginUser.get().getId();
                Space space = spaceService.getById(spaceId);
                // 只显示有空间id的图片
                pictureQueryRequest.setNullSpaceId(false);
                // 私有空间不需要审核
                pictureQueryRequest.setReviewStatus(null);
                ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
                if (SpaceTypeEnum.PRIVATE.getValue().equals(space.getSpaceType())) {
                    ThrowUtils.throwIf(!space.getUserId().equals(userId), ErrorCode.NOT_AUTH_ERROR, "无权限");
                }
                boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW);
                ThrowUtils.throwIf(!hasPermission, ErrorCode.NOT_AUTH_ERROR);
            }
            // 开启分页
            Page<Picture> page = Page.of(pictureQueryRequest.getPageNum(), pictureQueryRequest.getPageSize());

            LambdaQueryWrapper<Picture> pageQueryWrapper = this.buildPageQueryWrapper(pictureQueryRequest);
            Page<Picture> pageResult = this.page(page, pageQueryWrapper);
            // 返回结果
            List<PictureVO> pictureVOList = this.buildPictureVOListByPage(page.getRecords(), request);
            PageResult<PictureVO> result = new PageResult<PictureVO>()
                    .setPageNum(pageResult.getCurrent())
                    .setPageSize(pageResult.getSize())
                    .setTotal(pageResult.getTotal())
                    .setRecords(pictureVOList);
            return JSONUtil.toJsonStr(result);
        });
        return JSONUtil.toBean(cacheValue, PageResult.class);
    }

    private List<PictureVO> buildPictureVOListByPage(List<Picture> pictureList, HttpServletRequest request) {
        if (CollUtil.isEmpty(pictureList)) {
            return Collections.emptyList();
        }
        // 获取用户id,用户与图片是一对多的关系，去掉重复，避免重复查询
        Set<Long> userIds = pictureList.stream().map(Picture::getUserId).collect(Collectors.toSet());
        // 获取用户id对应的用户信息
        Map<Long, UserVO> userIdUserVOMap = userService.lambdaQuery()
                .in(User::getId, userIds).list().stream()
                .map(user -> BeanUtil.toBean(user, UserVO.class))
                .collect(Collectors.toMap(UserVO::getId, v -> v));
        // 封装PictureVOList
        return pictureList.stream().map(picture -> {
            PictureVO pictureVO = BeanUtil.toBean(picture, PictureVO.class);
            pictureVO.setUser(userIdUserVOMap.getOrDefault(picture.getUserId(), null));
            pictureVO.setTags(JSONUtil.toList(picture.getTags(), String.class));
            return pictureVO;
        }).collect(Collectors.toList());
    }

    @Override
    public Boolean updatePicture(PictureUpdateRequest pictureUpdateRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(pictureUpdateRequest == null || pictureUpdateRequest.getId() <= 0L, ErrorCode.PARAMS_ERROR);
        Picture picture = BeanUtil.toBean(pictureUpdateRequest, Picture.class);
        picture.setTags(JSONUtil.toJsonStr(pictureUpdateRequest.getTags()));
        // 数据校验
        this.validPicture(picture);
        // 判断更新数据是否存在
        Picture result = pictureMapper.selectById(picture.getId());
        ThrowUtils.throwIf(result == null, ErrorCode.NOT_FOUND_ERROR);
        UserLogin loginUser = userService.getLoginUser(request);
        // 补充一下审核参数,管理员直接通过，其他会自动变成待审核
        this.fillReviewParams(picture, loginUser);
        // 更新
        return pictureMapper.updateById(picture) > 0;
    }

    @Override
    public Boolean reviewPicture(PictureReviewRequest pictureReviewRequest, HttpServletRequest request) {
        // 获取审核人信息
        UserLogin loginUser = userService.getLoginUser(request);
        Long id = pictureReviewRequest.getId();
        Integer reviewStatus = pictureReviewRequest.getReviewStatus();
        PictureReviewStatusEnum reviewStatusEnum = PictureReviewStatusEnum.getEnumByValue(reviewStatus);
        // reviewStatusEnum为null时,提交的审核不在定义的规则内
        ThrowUtils.throwIf(id == null || reviewStatusEnum == null, ErrorCode.PARAMS_ERROR);
        // 获取旧的图片信息
        Picture oldPicture = pictureMapper.selectById(id);
        ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
        ThrowUtils.throwIf(reviewStatus.equals(oldPicture.getReviewStatus()), ErrorCode.PARAMS_ERROR, "请勿重复审核");
        // 更新审核状态
        Picture updatePicture = BeanUtil.toBean(pictureReviewRequest, Picture.class);
        updatePicture.setReviewerId(loginUser.getId());
        updatePicture.setReviewTime(LocalDateTime.now());
        return pictureMapper.updateById(updatePicture) > 0;
    }

    @Override
    public void fillReviewParams(Picture picture, UserLogin loginUser) {
        if (userService.isAdmin(loginUser)) {
            // 是管理员时
            picture.setReviewerId(loginUser.getId());
            picture.setReviewTime(LocalDateTime.now());
            picture.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
            picture.setReviewMessage("管理员自动过审");
        } else {
            // 非管理员,默认时待审核
            picture.setReviewStatus(PictureReviewStatusEnum.REVIEW_WAIT.getValue());
        }
    }

    @Override
    public Integer uploadPictureByBatch(PictureUploadByBatchRequest pictureUploadByBatchRequest, UserLogin loginUser) {
        ThrowUtils.throwIf(pictureUploadByBatchRequest == null, ErrorCode.PARAMS_ERROR);
        String searchText = pictureUploadByBatchRequest.getSearchText();
        Integer count = pictureUploadByBatchRequest.getCount();
        // 爬取数量控制在30条以免被封ip,因为这个爬虫最大固定了一页显示35条
        ThrowUtils.throwIf(count >= 30, ErrorCode.PARAMS_ERROR, "获取图片数量的在1-30内");
        ThrowUtils.throwIf(StrUtil.isBlank(searchText), ErrorCode.PARAMS_ERROR, "搜索关键字不能为空");
        // 随机生成页码
        int pageNum = RandomUtil.randomInt(1, 13);
        String fetchUrl = String.format(crawlerUrl, searchText, (pageNum - 1) * 35 + 1);
        Document document;
        try {
            // 1.发送请求获取真个页面
            document = Jsoup.connect(fetchUrl).get();
        } catch (IOException e) {
            log.error("获取页面失败", e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取页面失败");
        }
        Element div = document.getElementsByClass("dgControl").first();
        if (ObjectUtil.isNull(div)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取元素失败");
        }
        // 这里获取的时处理号的图而非原图
        // Elements elements = div.select("img.ming");
        // 获取图片原图
        Elements imgElements = div.select("a.iusc");

        int uploadCount = 0;
        for (Element element : imgElements) {
            // 拿到img元素
            // String imgUrl = element.attr("src")
            // 获取上面的m属性
            String mAttr = element.attr("m");
            HashMap<String, String> mMap = null;
            try {
                mMap = JSONUtil.toBean(mAttr, HashMap.class);
            } catch (Exception e) {
                log.info("获取图片信息失败:{}", mAttr);
                continue;
            }
            String imgUrl = mMap.get("murl");
            // 处理url
            int questionMarkIndex = imgUrl.lastIndexOf("?");
            imgUrl = questionMarkIndex > 0 ? imgUrl.substring(0, questionMarkIndex) : imgUrl;
            String imgName = mMap.getOrDefault("t", searchText + "(" + uploadCount + 1 + ")");
            if (StrUtil.isBlank(imgUrl)) {
                log.info("当前图片连接为空,已跳过:{}", imgUrl);
                continue;
            }
            // 上传图片
            PictureUploadRequest pictureUploadRequest = new PictureUploadRequest();
            pictureUploadRequest.setName(imgName);
            // 校验一下这个地址是否可以，一房应为这个上传失败而停止

            try (HttpResponse response = HttpUtil.createRequest(Method.HEAD, imgUrl).execute()) {
                if (!response.isOk()) {
                    log.error("图片地址无法访问:{}", imgUrl);
                    continue;
                }
            }
            PictureVO pictureVO = pictureService.uploadPicture(imgUrl, pictureUploadRequest, loginUser);
            if (pictureVO != null && pictureVO.getId() != null) {
                uploadCount++;
            }
            if (uploadCount >= count) {
                break;
            }
        }
        return uploadCount;
    }


    @Override
    public Boolean editPicture(PictureEditRequest pictureEditRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(pictureEditRequest == null || pictureEditRequest.getId() <= 0L, ErrorCode.PARAMS_ERROR);
        Picture picture = BeanUtil.toBean(pictureEditRequest, Picture.class);
        picture.setTags(JSONUtil.toJsonStr(pictureEditRequest.getTags()));
        // 设置编辑时间
        picture.setEditTime(LocalDateTime.now());
        // 数据校验
        this.validPicture(picture);
        UserLogin loginUser = userService.getLoginUser(request);
        // 判断更新数据是否存在
        Picture oldPicture = pictureMapper.selectById(picture.getId());
        ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
        // 只有当前图片的作者或者管理员可以编辑
        boolean condition = !oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser);
        ThrowUtils.throwIf(condition, ErrorCode.NOT_AUTH_ERROR);
        // 校验权限,用so-token注解替换掉了
        // this.checkPictureAuth(loginUser, oldPicture);
        // 填充审核参数
        this.fillReviewParams(picture, loginUser);
        // 有权限时，更新图片
        return pictureMapper.updateById(picture) > 0;
    }

    @Override
    public Boolean deletePicture(DeleteRequest deleteRequest, HttpServletRequest request) {
        // 参数校验
        ThrowUtils.throwIf(deleteRequest == null || deleteRequest.getId() <= 0L, ErrorCode.PARAMS_ERROR);
        UserLogin loginUser = userService.getLoginUser(request);
        // 查找图片是否存在
        Long userId = userService.getLoginUser(request).getId();
        Picture oldPicture = pictureMapper.selectById(deleteRequest.getId());
        ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
        // 校验权限,用so-token注解替换掉了
        // pictureService.checkPictureAuth(loginUser, oldPicture);

        // 有权限时
        transactionTemplate.execute(status -> {
            // 异步删除对象存储的图片
            clearPictureFile(oldPicture);
            boolean delete = pictureService.removeById(oldPicture);
            ThrowUtils.throwIf(!delete, ErrorCode.OPERATION_ERROR);
            Long spaceId = oldPicture.getSpaceId();
            if (spaceId != null) {
                // 只有私有空间才需要减去额度
                // 释放额度
                boolean update = spaceService.lambdaUpdate()
                        .eq(Space::getId, oldPicture.getSpaceId())
                        .setSql("total_count = total_count - 1")
                        .setSql("total_size = total_size - " + oldPicture.getPicSize())
                        .update();
                ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "更新额度失败");
            }
            return true;
        });

        return true;
    }

    @Async
    @Override
    public void clearPictureFile(Picture picture) {
        String url = picture.getUrl();
        Long count = pictureMapper.selectCount(new LambdaQueryWrapper<Picture>()
                .eq(Picture::getUrl, url)
        );
        // 表示不止一条记录关联该图片，不删除
        if (count > 1) {
            return;
        }
        if (StrUtil.isNotBlank(url)) {
            cosManager.deleteObject(urlToKey(url));
        }
        String thumbnailUrl = picture.getThumbnailUrl();
        String previewUrl = picture.getPreviewUrl();
        // 删除缩略图和预览图
        if (StrUtil.isNotBlank(thumbnailUrl)) {
            cosManager.deleteObject(urlToKey(thumbnailUrl));
        }
        if (StrUtil.isNotBlank(previewUrl)) {
            cosManager.deleteObject(urlToKey(previewUrl));
        }
    }

    private static String urlToKey(String url) {
        return url.substring(url.indexOf("/", url.indexOf("//") + 2) + 1);
    }

    @Override
    public void checkPictureAuth(UserLogin loginUser, Picture picture) {
        Long spaceId = picture.getSpaceId();
        if (spaceId == null) {
            // 公共图库仅本人或者管理员才可以操作
            ThrowUtils.throwIf(!userService.isAdmin(loginUser) && !loginUser.getId().equals(picture.getUserId()), ErrorCode.NOT_AUTH_ERROR);
        } else {
            // 私有空间，仅空间本人才可以操作
            ThrowUtils.throwIf(!loginUser.getId().equals(picture.getUserId()), ErrorCode.NOT_AUTH_ERROR);
        }
    }

    @Override
    public List<PictureVO> searchPictureByColor(SearchPictureByColorRequest searchPictureByColorRequest, UserLogin loginUser) {
        Space space = spaceService.getById(searchPictureByColorRequest.getSpaceId());
        ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
        ThrowUtils.throwIf(!space.getId().equals(searchPictureByColorRequest.getSpaceId()), ErrorCode.NOT_AUTH_ERROR, "没有访问空间权限");

        // 查询该空间下的所有图片(有主色调的)
        List<Picture> pictureList = pictureMapper.selectList(new LambdaQueryWrapper<Picture>()
                .eq(Picture::getSpaceId, searchPictureByColorRequest.getSpaceId())
                .isNotNull(Picture::getPicColor));
        if (CollUtil.isEmpty(pictureList)) {
            return Collections.emptyList();
        }
        // 将目标颜色转化成Color对象
        Color targetColor = Color.decode(searchPictureByColorRequest.getPicColor());
        return pictureList.stream().sorted(Comparator.comparingDouble(picture -> {
                    // 默认越小越靠前
                    // 没有主色调的放到最前
                    if (StrUtil.isBlank(picture.getPicColor())) {
                        return Double.MAX_VALUE;
                    }
                    Color pictureColor = Color.decode(picture.getPicColor());
                    // 越大越相似,正数值越大则相反数越小
                    return -ColorSimilarityUtils.weightedColorDifference(pictureColor, targetColor);
                }))
                // 截取12个
                .limit(12)
                // 转换成VO
                .map(PictureVO::objToVO)
                .collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean editPictureByBatch(PictureEditByBatchRequest pictureEditByBatchRequest, UserLogin loginUser) {
        // 校验参数
        validBatchEditRequest(pictureEditByBatchRequest, loginUser);
        List<Picture> pictureList = pictureMapper.selectList(new LambdaQueryWrapper<Picture>()
                .eq(Picture::getSpaceId, pictureEditByBatchRequest.getSpaceId())
                .in(Picture::getId, pictureEditByBatchRequest.getPictureIdList())
        );
        ThrowUtils.throwIf(CollUtil.isEmpty(pictureList), ErrorCode.NOT_FOUND_ERROR, "指定的图片不在该空间");
        // 根据nameRule 填充名称
        fillPictureWithNameRule(pictureList, pictureEditByBatchRequest.getNameRule());

        // 分批处理以免事务过长
        int batchSize = 100;
        ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
        for (int i = 0; i < pictureList.size(); i += batchSize) {
            int end = Math.min(i + batchSize, pictureList.size());
            List<Picture> batch = pictureList.subList(i, end);

            // 异步处理每批任务
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                batch.forEach(picture -> {
                    // 编辑标签和分类
                    if (pictureEditByBatchRequest.getCategory() != null) {
                        picture.setCategory(pictureEditByBatchRequest.getCategory());
                    }
                    if (pictureEditByBatchRequest.getTags() != null) {
                        picture.setTags(JSONUtil.toJsonStr(pictureEditByBatchRequest.getTags()));
                    }
                });
                boolean result = pictureService.updateBatchById(batch);
                if (!result) {
                    throw new BusinessException(ErrorCode.OPERATION_ERROR, "批量更新图片失败");
                }
            }, customExecutor);
            futures.add(future);
        }
        // 合并所有任务，等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        return Boolean.TRUE;
    }

    @Override
    public CreateOutPaintingTaskResponse createPictureOutPaintingTask(CreatePictureOutPaintingRequest createPictureOutPaintingRequest, UserLogin loginUser) {
        Picture picture = Optional.ofNullable(pictureService.getById(createPictureOutPaintingRequest.getPictureId()))
                .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR));
        // 用so-token注解替换了
        // checkPictureAuth(loginUser, picture);
        // 构建请求
        CreateOutPaintingTaskRequest taskRequest = new CreateOutPaintingTaskRequest();
        CreateOutPaintingTaskRequest.Input input = new CreateOutPaintingTaskRequest.Input();
        input.setImageUrl(picture.getUrl());
        taskRequest.setInput(input);
        taskRequest.setParameters(createPictureOutPaintingRequest.getParameters());
        // 创建任务
        return aliYunAiApi.createOutPaintingTask(taskRequest);
    }



    private void fillPictureWithNameRule(List<Picture> pictureList, String nameRule) {
        if (CollUtil.isEmpty(pictureList) || StrUtil.isBlank(nameRule)) {
            return;
        }
        long count=1;
        try {
            for (Picture picture : pictureList) {
                String pictureName = nameRule.replaceAll("\\{序号}", String.valueOf(count++));
                picture.setName(pictureName);
            }
        } catch (Exception e) {
            log.error("名称解析有误",e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "名称解析有误");
        }
    }

    private void validBatchEditRequest(PictureEditByBatchRequest pictureEditByBatchRequest, UserLogin loginUser) {
        ThrowUtils.throwIf(pictureEditByBatchRequest == null || pictureEditByBatchRequest.getSpaceId() <= 0, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(CollUtil.isEmpty(pictureEditByBatchRequest.getPictureIdList()), ErrorCode.PARAMS_ERROR);
        Space space = spaceService.lambdaQuery().eq(Space::getId, pictureEditByBatchRequest.getSpaceId()).one();
        ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
        ThrowUtils.throwIf(!space.getUserId().equals(loginUser.getId()), ErrorCode.NOT_AUTH_ERROR, "没有权限");
    }

    private Picture buildPicture(@Nullable Picture oldPicture, UploadPictureResult uploadPictureResult, Long userId, Long spaceId) {
        return Picture.builder()
                .id(oldPicture == null ? null : oldPicture.getId())
                .editTime(oldPicture == null ? null : LocalDateTime.now())
                .name(uploadPictureResult.getName())
                .url(uploadPictureResult.getUrl())
                .thumbnailUrl(uploadPictureResult.getThumbnailUrl())
                .previewUrl(uploadPictureResult.getPreviewUlr())
                .picFormat(uploadPictureResult.getPicFormat())
                .picHeight(uploadPictureResult.getPicHeight())
                .picWidth(uploadPictureResult.getPicWidth())
                .picScale(uploadPictureResult.getPicScale())
                .picSize(uploadPictureResult.getPicSize())
                .userId(userId)
                .spaceId(spaceId)
                .picColor(uploadPictureResult.getPicColor())
                .build();
    }

    private LambdaQueryWrapper<Picture> buildPageQueryWrapper(PictureQueryRequest pictureQueryRequest) {
        if (pictureQueryRequest == null) {
            return new LambdaQueryWrapper<>();
        }
        LambdaQueryWrapper<Picture> wrapper = new LambdaQueryWrapper<Picture>()
                .eq(pictureQueryRequest.getId() != null && pictureQueryRequest.getId() > 0L, Picture::getId, pictureQueryRequest.getId())
                .eq(pictureQueryRequest.getUserId() != null && pictureQueryRequest.getId() > 0L, Picture::getUserId, pictureQueryRequest.getUserId())
                // 需要模糊查询的，但左模糊查询会破环索引，要想保留索引用右模糊查询，或者用mongoDB、elasticsearch来提高搜索效率
                .like(StrUtil.isNotBlank(pictureQueryRequest.getName()), Picture::getName, pictureQueryRequest.getName())
                .like(StrUtil.isNotBlank(pictureQueryRequest.getIntroduction()), Picture::getIntroduction, pictureQueryRequest.getIntroduction())
                .like(StrUtil.isNotBlank(pictureQueryRequest.getPicFormat()), Picture::getPicFormat, pictureQueryRequest.getPicFormat())
                .eq(StrUtil.isNotBlank(pictureQueryRequest.getCategory()), Picture::getCategory, pictureQueryRequest.getCategory())
                .eq(pictureQueryRequest.getPicSize() != null, Picture::getPicSize, pictureQueryRequest.getPicSize())
                .eq(pictureQueryRequest.getPicWidth() != null, Picture::getPicWidth, pictureQueryRequest.getPicWidth())
                .eq(pictureQueryRequest.getPicHeight() != null, Picture::getPicHeight, pictureQueryRequest.getPicHeight())
                .eq(pictureQueryRequest.getPicScale() != null, Picture::getPicScale, pictureQueryRequest.getPicScale())
                // startEditTime<=x<endEditTime
                .ge(ObjectUtil.isNotEmpty(pictureQueryRequest.getStartEditTime()), Picture::getEditTime, pictureQueryRequest.getStartEditTime())
                .lt(ObjectUtil.isNotEmpty(pictureQueryRequest.getEndEditTime()), Picture::getEditTime, pictureQueryRequest.getEndEditTime())
                // 审核条件
                .eq(ObjectUtil.isNotEmpty(pictureQueryRequest.getReviewStatus()), Picture::getReviewStatus, pictureQueryRequest.getReviewStatus())
                .like(ObjectUtil.isNotEmpty(pictureQueryRequest.getReviewMessage()), Picture::getReviewMessage, pictureQueryRequest.getReviewMessage())
                .eq(ObjectUtil.isNotEmpty(pictureQueryRequest.getReviewerId()), Picture::getReviewerId, pictureQueryRequest.getReviewerId())
                .eq(ObjectUtil.isNotEmpty(pictureQueryRequest.getSpaceId()), Picture::getSpaceId, pictureQueryRequest.getSpaceId());
        if (pictureQueryRequest.getNullSpaceId() != null) {
            // 没有的话就查询所有
            wrapper.isNull(pictureQueryRequest.getNullSpaceId(), Picture::getSpaceId);
        }
        // 组装tags的查询条件，JSON数组查询,json本质上是一个字符串，索引将集合的每个元素作为条件模糊查询
        List<String> tags = pictureQueryRequest.getTags();
        if (CollUtil.isNotEmpty(tags)) {
            for (String tag : tags) {
                wrapper.like(Picture::getTags, tag);
            }
        }
        // 多字段查询，searchText对name和introduction进行模糊查询
        wrapper.and(StrUtil.isNotBlank(pictureQueryRequest.getSearchText()),
                pictureLambdaQueryWrapper -> pictureLambdaQueryWrapper.like(Picture::getName, pictureQueryRequest.getSearchText())
                        .or()
                        .like(Picture::getIntroduction, pictureQueryRequest.getSearchText()));
        // 排序
        List<Integer> sortOrders = pictureQueryRequest.getSortOrders();
        List<String> sortFields = pictureQueryRequest.getSortFields();
        if (CollUtil.isNotEmpty(sortFields) && CollUtil.isNotEmpty(sortOrders)) {
            for (int i = 0; i < sortFields.size(); i++) {
                String sortField = sortFields.get(i);
                // sortFiled没有对应的order时，默认升序
                Integer order = i < sortOrders.size() ? sortOrders.get(i) : SortEnum.ASC.getCode();
                SFunction<Picture, Object> func = this.getSortFunction(sortField);
                // 拼接排序条件
                wrapper.orderBy(true, SortEnum.ASC.getCode().equals(order), func);
            }
        }
        return wrapper;

    }

    /**
     * 根据排序字段获取对应的函数式方法引用
     *
     * @param sortField 排序字段名
     * @return SFunction<Picture, Object>
     */
    private SFunction<Picture, Object> getSortFunction(String sortField) {
        SFunction<Picture, Object> result = null;
        switch (sortField) {
            case "id":
                result = Picture::getId;
                break;
            case "name":
                result = Picture::getName;
                break;
            case "introduction":
                result = Picture::getIntroduction;
                break;
            case "category":
                result = Picture::getCategory;
                break;
            case "picSize":
                result = Picture::getPicSize;
                break;
            case "picWidth":
                result = Picture::getPicWidth;
                break;
            case "picHeight":
                result = Picture::getPicHeight;
                break;
            case "picScale":
                result = Picture::getPicScale;
                break;
            case "createTime":
                result = Picture::getCreateTime;
                break;
            case "editTime":
                result = Picture::getEditTime;
                break;
        }
        return result;
    }

    /**
     * 校验图片信息
     */
    private void validPicture(Picture picture) {
        ThrowUtils.throwIf(picture == null, ErrorCode.PARAMS_ERROR);
        // 按照需求对图片信息进行校验
        ThrowUtils.throwIf(ObjectUtils.isNull(picture.getId()), ErrorCode.PARAMS_ERROR, "id不能为空");
        if (StrUtil.isNotBlank(picture.getUrl())) {
            ThrowUtils.throwIf(picture.getUrl().length() > 1024, ErrorCode.PARAMS_ERROR, "url过长，不能超过1024");
        }
        if (StrUtil.isNotBlank(picture.getIntroduction())) {
            ThrowUtils.throwIf(picture.getName().length() > 800, ErrorCode.PARAMS_ERROR, "简介过长，不能超过800");
        }
        //....
    }
}




